# -*- coding: utf-8 -*-
import logging
from functools import partial
from decimal import Decimal

import itertools

from common.utils import math_helper
from common.lottery.cyclical.ssc.model import BET_TYPE
from common.lottery.cyclical.ssc.model import SSC_LOTTERY_TYPES

_LOGGER = logging.getLogger('bigbang')

RATE_CONFIG = {  # base on UNIT==2
    BET_TYPE.CHOSEN_ONE: 10.000,
    BET_TYPE.TWO_BACK_DIRECT: 100.000,
    BET_TYPE.TWO_BACK_GROUP: 50.000,
    BET_TYPE.TWO_BACK_SUM: 100.000,
    BET_TYPE.THREE_FRONT_DIRECT: 1000.000,
    BET_TYPE.THREE_FRONT_GROUP_THREE: 333.333,
    BET_TYPE.THREE_FRONT_GROUP_SIX: 166.667,
    BET_TYPE.THREE_MIDDLE_DIRECT: 1000.000,
    BET_TYPE.THREE_MIDDLE_GROUP_THREE: 333.333,
    BET_TYPE.THREE_MIDDLE_GROUP_SIX: 166.667,
    BET_TYPE.THREE_BACK_DIRECT: 1000.000,
    BET_TYPE.THREE_BACK_GROUP_THREE: 333.333,
    BET_TYPE.THREE_BACK_GROUP_SIX: 166.667,
    BET_TYPE.FOUR_FRONT_DIRECT: 10000.000,
    BET_TYPE.FOUR_BACK_DIRECT: 10000.000,
    BET_TYPE.FIVE_DIRECT: 100000.000,
    BET_TYPE.FIVE_ALL: (20440, 220, 40, 20),
    BET_TYPE.TWO_BSOE: 4.000,
    BET_TYPE.FUN_2: 1.433,
    BET_TYPE.FUN_3: 11.683,
    BET_TYPE.FUN_4: 217.391,

    BET_TYPE.PLATE_SUM_BSOE: 2.000,
    BET_TYPE.PLATE_BOSE_1: 2.000,
    BET_TYPE.PLATE_BOSE_2: 2.000,
    BET_TYPE.PLATE_BOSE_3: 2.000,
    BET_TYPE.PLATE_BOSE_4: 2.000,
    BET_TYPE.PLATE_BOSE_5: 2.000,
    BET_TYPE.PLATE_DRAGON_TIGER_1_5: (2.22, 10),
}

UNIT = 2  # 彩票单价


def valid_number(bet_type, number):
    '''同一位之间用','隔开
       不同位之间用'|'隔开
       与顺序无关的选项，使用','隔开
    '''
    positions = number.split('|')
    nums = []
    for position in positions:
        if not position:
            continue
        ds = position.split(',')
        for d in ds:
            if bet_type in [BET_TYPE.TWO_BACK_SUM]:
                assert 0 <= int(d) <= 18, 'two sum name range error'
            elif bet_type in [BET_TYPE.TWO_BSOE, BET_TYPE.PLATE_SUM_BSOE, BET_TYPE.PLATE_BOSE_1, BET_TYPE.PLATE_BOSE_2,
                              BET_TYPE.PLATE_BOSE_3, BET_TYPE.PLATE_BOSE_4, BET_TYPE.PLATE_BOSE_5]:
                # big: 大, small: 小, odd: 单, even: 双
                assert d in ('b', 's', 'o', 'e'), 'number format wrong'
            elif bet_type in [BET_TYPE.PLATE_DRAGON_TIGER_1_5]:
                assert d in ('t', 'd', 'h'), 'number format wrong'
            else:
                assert 0 <= int(d) <= 9, 'number range error'
        nums.append(ds)
    if bet_type == BET_TYPE.CHOSEN_ONE:  # 定位胆, ||||5     |2|||
        assert len(positions) == 5 and len(nums) >= 1, 'format wrong'
    elif bet_type == BET_TYPE.TWO_BACK_DIRECT:  # 后二直选    2|3    2,3|1
        assert len(positions) == 2, 'two single number format error'
    elif bet_type == BET_TYPE.TWO_BACK_GROUP:  # 后二组选复式   2,3,4
        assert len(positions) == 1 and len(nums) == 1, 'two combine number format error'
        assert len(set(nums[0])) == len(nums[0]), 'two combine number no dup'
    elif bet_type == BET_TYPE.TWO_BACK_SUM:  # 后二直选和值     5,9
        assert len(positions) == 1 and len(nums) == 1, 'two sum number need no `|`'
        assert 19 >= len(nums[0]) >= 0, 'two sum number format wrong'
    elif bet_type == BET_TYPE.THREE_FRONT_DIRECT:  # 前三直选     3|4|5    1,3|4|5
        assert len(positions) == 3, 'three front direct number format error'
    elif bet_type == BET_TYPE.THREE_FRONT_GROUP_THREE:  # 前三组三  2,3     2,3,4
        assert len(positions) == 1 and len(nums) == 1, 'three group multi number format error'
        assert len(nums[0]) >= 2, 'three group multi number should at least 2'
        assert len(set(nums[0])) == len(nums[0]), 'three group multi number dup'
    elif bet_type == BET_TYPE.THREE_FRONT_GROUP_SIX:  # 前三组六  2,3,4      4,5,6,7
        assert len(positions) == 1 and len(nums[0]) >= 3, 'three front group six number format error'
    elif bet_type == BET_TYPE.THREE_MIDDLE_DIRECT:  # 中三直选  2|3|4    2|3,4|5
        assert len(positions) == 3, 'three middle direct number format error'
    elif bet_type == BET_TYPE.THREE_MIDDLE_GROUP_THREE:  # 中三选组三  2,3   2,3,4
        assert len(positions) == 1 and len(nums) == 1, 'three group multi number format error'
        assert len(nums[0]) >= 2, 'three group multi number should at least 2'
        assert len(set(nums[0])) == len(nums[0]), 'three group multi number dup'
    elif bet_type == BET_TYPE.THREE_MIDDLE_GROUP_SIX:  # 中三组六
        assert len(positions) == 1 and len(nums[0]) >= 3, 'three front group six number format error'
    elif bet_type == BET_TYPE.THREE_BACK_DIRECT:  # 后三直选 3|4|5     1,2|3|4|5
        assert len(positions) == 3, 'three back direct number format error'
    elif bet_type == BET_TYPE.THREE_BACK_GROUP_THREE:  # 后三组三  3,4    3,4,5
        assert len(positions) == 1 and len(nums) == 1, 'three group multi number format error'
        assert len(nums[0]) >= 2, 'three group multi number should at least 2'
        assert len(set(nums[0])) == len(nums[0]), 'three group multi number dup'
    elif bet_type == BET_TYPE.THREE_BACK_GROUP_SIX:  # 后三组六  3,4,5      3,4,5,6
        assert len(positions) == 1 and len(nums[0]) >= 3, 'three front group six number format error'
    elif bet_type == BET_TYPE.FOUR_FRONT_DIRECT:  # 前四直选 1|2|3|4   1,2|3|4|5
        assert len(positions) == 4, 'four front direct number format error'
    elif bet_type == BET_TYPE.FOUR_BACK_DIRECT:  # 后四直选  1|2|3|4   1,2|3|4|5
        assert len(positions) == 4, 'four front direct number format error'
    elif bet_type == BET_TYPE.FIVE_DIRECT:  # 五星直选  1|2|3|4|5   1,2|3|4|5|6
        assert len(positions) == 5 and len(nums) == 5
    elif bet_type == BET_TYPE.FIVE_ALL:  # 五星通选
        assert len(positions) == 5 and len(nums) == 5


def calc_total_price(bet_type, number, times=1, unit=UNIT):
    '''根据不同的竞猜方式计算所需总价
    '''
    positions = number.split('|')
    nums = []
    for position in positions:
        if not position:
            continue
        ds = position.split(',')
        nums.append(ds)

    if bet_type in (
            BET_TYPE.CHOSEN_ONE, BET_TYPE.TWO_BACK_DIRECT, BET_TYPE.THREE_FRONT_DIRECT,
            BET_TYPE.THREE_MIDDLE_DIRECT, BET_TYPE.THREE_BACK_DIRECT,
            BET_TYPE.FOUR_FRONT_DIRECT, BET_TYPE.FOUR_BACK_DIRECT,
            BET_TYPE.FIVE_DIRECT,
            BET_TYPE.FIVE_ALL,
            BET_TYPE.TWO_BSOE,
            BET_TYPE.FUN_2, BET_TYPE.FUN_3, BET_TYPE.FUN_4, BET_TYPE.PLATE_SUM_BSOE, BET_TYPE.PLATE_BOSE_1,
            BET_TYPE.PLATE_BOSE_2, BET_TYPE.PLATE_BOSE_3, BET_TYPE.PLATE_BOSE_4, BET_TYPE.PLATE_BOSE_5,
            BET_TYPE.PLATE_DRAGON_TIGER_1_5):
        # 这几种都是用逗号直接分割，总注数等于各串长度之乘积
        price = 0 if bet_type == BET_TYPE.CHOSEN_ONE else 1
        for num in nums:
            if bet_type == BET_TYPE.CHOSEN_ONE:  # 定位胆的注数是和值
                price += len(num)
            else:
                price *= len(num)
        return price * times * unit
    elif bet_type == BET_TYPE.TWO_BACK_GROUP:
        # 后二组选复式，一个字符串，个位十位所有数字的组合，结果为C(n, 2)
        n = len(nums[0])
        return math_helper.c(n, 2) * times * unit
    elif bet_type in (
            BET_TYPE.THREE_FRONT_GROUP_THREE,
            BET_TYPE.THREE_MIDDLE_GROUP_THREE,
            BET_TYPE.THREE_BACK_GROUP_THREE):
        # 前，中，后三组三
        n = len(nums[0])
        return math_helper.c(n, 2) * 2 * times * unit
    elif bet_type in (
            BET_TYPE.THREE_FRONT_GROUP_SIX,
            BET_TYPE.THREE_MIDDLE_GROUP_SIX,
            BET_TYPE.THREE_BACK_GROUP_SIX):
        # 前，中，后三组六
        return math_helper.c(len(nums[0]), 3) * times * unit
    elif bet_type == BET_TYPE.TWO_BACK_SUM:
        # 二星和值，这个对于每个数字，出现的概率是固定的
        count = 0
        for num in nums[0]:
            n = int(num)
            if n < 10:
                count += (n + 1)
            else:
                count += (19 - n)
        return count * times * unit
    else:
        assert False, 'bet_type %s invalid' % bet_type


def generate_win_func(number):
    '''根据开奖结果生成根据押注方式对应的中奖函数，避免重复计算
    '''
    result = {}

    def n_direct_back(n, x):
        positions = x.split('|')
        is_in = True
        for i, v in enumerate(number[-n:]):
            if v not in positions[i]:
                is_in = False
                break
        return 1 if is_in else 0

    result[BET_TYPE.TWO_BACK_DIRECT] = partial(n_direct_back, 2)
    result[BET_TYPE.THREE_BACK_DIRECT] = partial(n_direct_back, 3)
    result[BET_TYPE.FOUR_BACK_DIRECT] = partial(n_direct_back, 4)
    result[BET_TYPE.FIVE_DIRECT] = partial(n_direct_back, 5)

    def n_direct_front(n, x):
        positions = x.split('|')
        is_in = True
        for i, v in enumerate(number[0:n]):
            if v not in positions[i]:
                is_in = False
                break
        return 1 if is_in else 0

    result[BET_TYPE.THREE_FRONT_DIRECT] = partial(n_direct_front, 3)
    result[BET_TYPE.FOUR_FRONT_DIRECT] = partial(n_direct_front, 4)

    def three_direct_middle(x):
        positions = x.split('|')
        is_in = True
        for i, v in enumerate(number[1:4]):
            if v not in positions[i]:
                is_in = False
                break
        return 1 if is_in else 0

    result[BET_TYPE.THREE_MIDDLE_DIRECT] = three_direct_middle

    def two_back_group(x):
        if number[-1] == number[-2]:
            return 0
        guesses = x.split(',')
        if sum([guess in number[-2:] for guess in guesses]) == 2:
            return 1
        return 0

    result[BET_TYPE.TWO_BACK_GROUP] = two_back_group

    def group_three(n, x):
        nums = x.split(',')
        return 1 if len(set(number[n:n + 3])) == 2 and len(set(number[n:n + 3]) & set(nums)) == 2 else 0

    result[BET_TYPE.THREE_FRONT_GROUP_THREE] = partial(group_three, 0)
    result[BET_TYPE.THREE_MIDDLE_GROUP_THREE] = partial(group_three, 1)
    result[BET_TYPE.THREE_BACK_GROUP_THREE] = partial(group_three, 2)

    def group_six(n, x):
        nums = x.split(',')
        return 1 if len(set(number[n: n + 3])) == 3 and len(set(number[n:n + 3]) & set(nums)) == 3 else 0

    result[BET_TYPE.THREE_FRONT_GROUP_SIX] = partial(group_six, 0)
    result[BET_TYPE.THREE_MIDDLE_GROUP_SIX] = partial(group_six, 1)
    result[BET_TYPE.THREE_BACK_GROUP_SIX] = partial(group_six, 2)

    def chosen_one(x):
        xs = x.split('|')
        r = 0
        for i, v in enumerate(xs):
            hit = number[i]
            if hit in v:
                r += 1
        return r

    result[BET_TYPE.CHOSEN_ONE] = chosen_one

    def five_all(x):
        '''和其他不同，这里返回的是命中的序列，即全中、前后三、前后二,
           没中的话，返回-1
        '''

        def _win_index(nums, win_number):
            '''
            判断单注号码中奖序号 eg, nums=('0', '9', '0', '0', '4')
            '''
            if nums == win_number:
                return 0
            if nums[:3] == win_number[:3] or nums[-3:] == win_number[-3:]:
                return 1
            if nums[:2] == win_number[:2] and nums[-2:] == win_number[-2:]:
                return 2
            if nums[:2] == win_number[:2] or nums[-2:] == win_number[-2:]:
                return 3
            return -1

        positions = x.split('|')
        nums = []
        win_list = []
        for position in positions:
            if not position:
                continue
            ds = position.split(',')
            nums.append(ds)
        for item in itertools.product(*nums):
            item = ''.join(item)
            index = _win_index(item, number)
            if index >= 0:
                win_list.append(index)
        return win_list

    result[BET_TYPE.FIVE_ALL] = five_all

    def two_bsoe(x):
        last_2 = number[-2:]
        desc = [set(), set()]
        for i, letter in enumerate(last_2):
            n = int(letter)
            if n >= 5:
                desc[i].add('b')
            else:
                desc[i].add('s')
            if n % 2 == 0:
                desc[i].add('e')
            else:
                desc[i].add('o')

        guesses = x.split('|')
        hit = 1
        for i, guess in enumerate(guesses):
            t = guess.split(',')
            hit *= len(set(t) & desc[i])
        return hit

    result[BET_TYPE.TWO_BSOE] = two_bsoe

    def two_sum(x):
        guesses = x.split(',')
        real_sum = str(int(number[-1]) + int(number[-2]))
        return 1 if real_sum in guesses else 0

    result[BET_TYPE.TWO_BACK_SUM] = two_sum

    def fun(n, x):
        guesses = x.split(',')
        hits = 0
        for guess in guesses:
            if sum([guess == num for num in number]) >= n:
                hits += 1
        return hits

    result[BET_TYPE.FUN_2] = partial(fun, 2)
    result[BET_TYPE.FUN_3] = partial(fun, 3)
    result[BET_TYPE.FUN_4] = partial(fun, 4)

    def n_plate_bsoe(n, x):
        num = int(number[n])
        desc = set()
        if num >= 5:
            desc.add('b')
        else:
            desc.add('s')
        if num % 2 == 0:
            desc.add('e')
        else:
            desc.add('o')
        guesses = x.split('|')
        hit = 1
        for i, guess in enumerate(guesses):
            t = guess.split(',')
            hit *= len(set(t) & desc)
        return hit

    result[BET_TYPE.PLATE_BOSE_1] = partial(n_plate_bsoe, 0)
    result[BET_TYPE.PLATE_BOSE_2] = partial(n_plate_bsoe, 1)
    result[BET_TYPE.PLATE_BOSE_3] = partial(n_plate_bsoe, 2)
    result[BET_TYPE.PLATE_BOSE_4] = partial(n_plate_bsoe, 3)
    result[BET_TYPE.PLATE_BOSE_5] = partial(n_plate_bsoe, 4)

    def sum_plate_bsoe(x):
        num = sum([int(n) for n in number])
        desc = set()
        if num >= 23:
            desc.add('b')
        else:
            desc.add('s')
        if num % 2 == 0:
            desc.add('e')
        else:
            desc.add('o')
        guesses = x.split('|')
        hit = 1
        for i, guess in enumerate(guesses):
            t = guess.split(',')
            hit *= len(set(t) & desc)
        return hit

    result[BET_TYPE.PLATE_SUM_BSOE] = sum_plate_bsoe

    def n_plate_dragon_tiger(n, x):
        guesses = x.split(',')
        dragon = int(number[n])
        tiger = int(number[-(n + 1)])
        if dragon > tiger:
            res = 'd'
            return 0 if res in guesses else -1
        elif dragon < tiger:
            res = 't'
            return 0 if res in guesses else -1
        else:
            res = 'h'
            return 1 if res in guesses else -1

    result[BET_TYPE.PLATE_DRAGON_TIGER_1_5] = partial(n_plate_dragon_tiger, 0)

    return result


def calc_win_price(funcs, bet_number, bet_type, times=1, unit=UNIT,
                   rate_config=RATE_CONFIG):
    '''根据开奖结果和押注方式/倍数，计算中奖金额
    '''
    func = funcs.get(bet_type)
    base = 0
    if bet_type in [BET_TYPE.FIVE_ALL]:
        index_list = func(bet_number)
        for index in index_list:
            if index >= 0:
                base += Decimal(RATE_CONFIG.get(bet_type)[index])
    elif bet_type == BET_TYPE.PLATE_DRAGON_TIGER_1_5:
        index = func(bet_number)
        if index >= 0:
            base += Decimal(RATE_CONFIG.get(bet_type)[index])
    else:
        base = func(bet_number) * Decimal(RATE_CONFIG.get(bet_type))

    return Decimal(base) * Decimal(times) * Decimal(unit) / Decimal(UNIT)


def ssc_calc_win_price_decorator(old_func):
    def outer(*args, **kwargs):
        if args[2] in BET_TYPE.values():  # args[2] is bet_type
            return calc_win_price(*args, **kwargs)
        else:
            return old_func(*args, **kwargs)

    return outer


def ssc_valid_number_decorator(old_func):
    def outer(bet_type, number):
        if bet_type in BET_TYPE.values():
            return valid_number(bet_type, number)
        else:
            return old_func(bet_type, number)

    return outer


def ssc_calc_total_price_decorator(old_func):
    def outer(bet_type, number, times=1, unit=UNIT):
        if bet_type in BET_TYPE.values():
            return calc_total_price(bet_type, number, times, unit)
        else:
            return old_func(bet_type, number, times, unit)

    return outer


def ssc_generate_win_func_decorator(old_func):
    def outer(number):
        all_func = {}
        all_func.update(old_func(number))
        all_func.update(generate_win_func(number))
        return all_func

    return outer


def ssc_get_base_reward_decorator(old_func):
    def outer(activity_type, bet_type):
        if activity_type in SSC_LOTTERY_TYPES and bet_type in BET_TYPE.values():
            config = RATE_CONFIG
            return config.get(bet_type, 0)
        else:
            return old_func(activity_type, bet_type)

    return outer


def ssc_calc_miss(trend, save, number, last_stats):
    def calc_func(last, i):
        return str(int(last[i]) + 1) if last[i] != 'x' else 'x'

    # 组选
    config = {
        'miss_%s' % BET_TYPE.TWO_BACK_GROUP: (-2, 5),  # (-2, 5) 是number切片头尾
        'miss_%s' % BET_TYPE.THREE_BACK_GROUP_THREE: (-3, 5),
        'miss_%s' % BET_TYPE.THREE_BACK_GROUP_SIX: (-3, 5),
        'miss_%s' % BET_TYPE.THREE_MIDDLE_GROUP_THREE: (1, 4),
        'miss_%s' % BET_TYPE.THREE_MIDDLE_GROUP_SIX: (1, 4),
        'miss_%s' % BET_TYPE.THREE_FRONT_GROUP_THREE: (0, 3),
        'miss_%s' % BET_TYPE.THREE_FRONT_GROUP_SIX: (0, 3)
    }
    for k, v in config.iteritems():
        last = getattr(last_stats, k).split(',')
        hit = number[v[0]: v[1]]
        for i in xrange(len(last)):
            last[i] = calc_func(last, i)
        for x in hit:
            last[int(x)] = '0'
        if 'x' in last:
            save = False
        setattr(trend, k, ','.join(last))

    # 大小单双
    hit = number[-2:]
    lasts = getattr(last_stats, 'miss_%s' % BET_TYPE.TWO_BSOE).split('|')
    currents = [['0', '0', '0', '0']] * 2

    for i, v in enumerate(lasts):
        current = currents[i]
        last = lasts[i].split(',')
        if int(hit[i]) >= 5:  # 大
            current[1] = calc_func(last, 1)
        else:
            current[0] = calc_func(last, 0)
        if int(hit[i]) % 2 == 0:  # 双
            current[2] = calc_func(last, 2)
        else:
            current[3] = calc_func(last, 3)
        if 'x' in current:
            save = False

    setattr(trend, 'miss_%s' % BET_TYPE.TWO_BSOE, '|'.join([','.join(k) for k in currents]))

    # 二星组选和值
    hit = int(number[-1]) + int(number[-2])
    last = getattr(last_stats, 'miss_%s' % BET_TYPE.TWO_BACK_SUM).split(',')
    for i in xrange(len(last)):
        last[i] = calc_func(last, i)
    last[hit] = '0'
    if 'x' in last:
        save = False
    setattr(trend, 'miss_%s' % BET_TYPE.TWO_BACK_SUM, ','.join(last))

    # 趣味玩法
    config = {
        'miss_%s' % BET_TYPE.FUN_2: 2,
        'miss_%s' % BET_TYPE.FUN_3: 3,
        'miss_%s' % BET_TYPE.FUN_4: 4
    }
    for k, v in config.iteritems():
        hits = [number.count(str(x)) >= v for x in range(0, 10)]
        last = getattr(last_stats, k).split(',')
        print k, v, last
        for i in range(len(last)):
            last[i] = calc_func(last, i)
            if hits[i]:
                last[i] = '0'
        if 'x' in last:
            save = False
        print k, v, last
        setattr(trend, k, ','.join(last))
